#ifndef __SIM_UTIL_H__
#define __SIM_UTIL_H__

#include "RandomLib/Random.hpp"
#include "RandomLib/NormalDistribution.hpp"
#include "RandomLib/RandomSelect.hpp"
#include <vector>
#include <string>
#include <cstring>
#include <cstdio>
#include "types.h"

extern RandomLib::Random *RAND;
extern RandomLib::NormalDistribution<float> *NORMDIST;
extern RandomLib::RandomSelect<float> *SELECTDIST;

////////////////////////////////////////////////////////////////////////////////
// tokenize
////////////////////////////////////////////////////////////////////////////////
// Parses the input string, and breaks up into tokens. Used in getting commands
// from the user in interactive mode.
// Inputs: string, delimiters
// Outputs: tokens
/* Borrowed from:
 * http://www.oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html
 */
class StringTokenizer {
  public:
  static void tokenize(const std::string& str, std::vector<std::string>& tokens,
                       const std::string& delimiters = " ")
  {
    std::string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    std::string::size_type pos     = str.find_first_of(delimiters, lastPos);

    while (std::string::npos != pos || std::string::npos != lastPos) {
      tokens.push_back(str.substr(lastPos, pos - lastPos));
      lastPos = str.find_first_not_of(delimiters, pos);
      pos = str.find_first_of(delimiters, lastPos);
    }
  }
};

class sim_util {
  public:
  static float norm(const float mean, const float sd) {
    return (*NORMDIST)(*RAND, mean, sd);
  }
  
  template<typename InputIterator> 
  static unsigned int select(InputIterator begin, InputIterator end) {
    SELECTDIST->Init(begin, end);
    return (*SELECTDIST)(*RAND);
  }
  
  //SelecteeType can be anything that has [] defined (vectors, maps, arrays)
  template<typename InputIterator, typename ReturnType>
  static ReturnType select(InputIterator begin, InputIterator end, ReturnType *choices) {
    return choices[sim_util::select(begin, end)];
  }

  static void transformSelectDistributions(graph_t &g) {
    using namespace boost;
    graph_traits<graph_t>::vertex_iterator vi, vi_end;
    for(tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) {
      std::string s = get(vertex_delay_dist_string, g, *vi);
      if(s.length() != 0) {
        std::vector<std::string> tokens;
        StringTokenizer::tokenize(s, tokens, ", ");
        std::vector<float> delays, weights;
        for(std::vector<std::string>::const_iterator it = tokens.begin(), end = tokens.end(); it != end; ++it) {
          delays.push_back(atof(it->c_str()));
          //printf("%s ", it->c_str());
          ++it;
          weights.push_back(atof(it->c_str()));
          //printf("%s\n", it->c_str());
        }
        put(vertex_delay_dist_delays, g, *vi, delays);
        put(vertex_delay_dist_weights, g, *vi, weights);
      }
    }

    graph_traits<graph_t>::edge_iterator ei, ei_end;
    for(tie(ei, ei_end) = edges(g); ei != ei_end; ++ei) {
      std::string s = get(edge_delay_dist_string, g, *ei);
      if(s.length() != 0) {
        std::vector<std::string> tokens;
        StringTokenizer::tokenize(s, tokens, ", ");
        std::vector<float> delays, weights;
        for(std::vector<std::string>::const_iterator it = tokens.begin(), end = tokens.end(); it != end; ++it) {
          delays.push_back(atof(it->c_str()));
          //printf("%s ", it->c_str());
          ++it;
          weights.push_back(atof(it->c_str()));
          //printf("%s\n", it->c_str());
        }
        put(edge_delay_dist_delays, g, *ei, delays);
        put(edge_delay_dist_weights, g, *ei, weights);
      }
    }
    
  }

  static float getVertexMeanDelay(graph_t &g, Vertex &v) {
    using namespace boost;
    if(get(vertex_delay_dist_string, g, v).length() == 0) {
      return get(vertex_delay_mean, g, v);
    }
    else {
      double delaytotal = 0.0f;
      double weighttotal = 0.0f;
      std::vector<float> &delays = get(vertex_delay_dist_delays, g, v);
      std::vector<float> &weights = get(vertex_delay_dist_weights, g, v);
      assert(delays.size() == weights.size());
      for(unsigned int i = 0; i < delays.size(); i++) {
        delaytotal += (delays[i]*weights[i]);
        weighttotal += weights[i];
      }
      return delaytotal/weighttotal;
    }
  }

  static float getEdgeMeanDelay(graph_t &g, Edge &e) {
    using namespace boost;
    if(get(edge_delay_dist_string, g, e).length() == 0) {
      return get(edge_delay_mean, g, e);
    }
    else {
      double delaytotal = 0.0f;
      double weighttotal = 0.0f;
      std::vector<float> &delays = get(edge_delay_dist_delays, g, e);
      std::vector<float> &weights = get(edge_delay_dist_weights, g, e);
      assert(delays.size() == weights.size());
      for(unsigned int i = 0; i < delays.size(); i++) {
        delaytotal += (delays[i]*weights[i]);
        weighttotal += weights[i];
      }
      return delaytotal/weighttotal;
    }
  }  
};

#endif